/**
 * Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.jfinal.core;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.jfinal.aop.Interceptor;
import com.jfinal.aop.InterceptorManager;
import com.jfinal.config.Interceptors;
import com.jfinal.config.Routes;
import com.jfinal.config.Routes.Route;

/**
 * ActionMapping
 */
final class ActionMapping {
	
	private static final String SLASH = "/";
	private Routes routes;
	// private Interceptors interceptors;
	
	private final Map<String, Action> mapping = new HashMap<String, Action>();
	
	ActionMapping(Routes routes, Interceptors interceptors) {
		this.routes = routes;
		// this.interceptors = interceptors;
	}
	
	private Set<String> buildExcludedMethodName() {
		/*Set<String> excludedMethodName = new HashSet<String>();
		Method[] methods = Controller.class.getMethods();
		for (Method m : methods) {
			if (m.getParameterTypes().length == 0)
				excludedMethodName.add(m.getName());
		}*/
		Method[] methods = Controller.class.getMethods();
		HashSet<Method> newHashSet = Sets.newHashSet(methods);
		// 获取参数长度为0的方法的名字（无参方法）
		Set<String> excludedMethodName = newHashSet.stream()
				.filter(item -> item.getParameterTypes().length == 0)
				.map(item -> item.getName())
				.collect(Collectors.toSet());
		
		return excludedMethodName;
	}
	
	private List<Routes> getRoutesList() {
		List<Routes> routesList = Routes.getRoutesList();
		// 设定list的初始大小，减少开销
		List<Routes> ret = new ArrayList<Routes>(routesList.size() + 1);
		ret.add(routes);
		ret.addAll(routesList);
		return ret;
	}
	
	void buildActionMapping() {
		// 清空一下
		mapping.clear();
		// 获取Controller中没有参数的方法
		Set<String> excludedMethodName = buildExcludedMethodName();
		// 拦截器
		InterceptorManager interMan = InterceptorManager.me();
		/*for (Routes routes : getRoutesList()) {
			for (Route route : routes.getRouteItemList()) {
				Class<? extends Controller> controllerClass = route.getControllerClass();
				Interceptor[] controllerInters = interMan.createControllerInterceptor(controllerClass);
				
				boolean sonOfController = (controllerClass.getSuperclass() == Controller.class);
				Method[] methods = (sonOfController ? controllerClass.getDeclaredMethods() : controllerClass.getMethods());
				for (Method method : methods) {
					String methodName = method.getName();
					// 如果这是一个无参的方法
					if (excludedMethodName.contains(methodName) || method.getParameterTypes().length != 0)
						continue ;
					if (sonOfController && !Modifier.isPublic(method.getModifiers()))
						continue ;
					
					Interceptor[] actionInters = interMan.buildControllerActionInterceptor(routes.getInterceptors(), controllerInters, controllerClass, method);
					String controllerKey = route.getControllerKey();
					
					ActionKey ak = method.getAnnotation(ActionKey.class);
					String actionKey;
					if (ak != null) {
						actionKey = ak.value().trim();
						if ("".equals(actionKey))
							throw new IllegalArgumentException(controllerClass.getName() + "." + methodName + "(): The argument of ActionKey can not be blank.");
						
						if (!actionKey.startsWith(SLASH))
							actionKey = SLASH + actionKey;
					}
					else if (methodName.equals("index")) {
						actionKey = controllerKey;
					}
					else {
						actionKey = controllerKey.equals(SLASH) ? SLASH + methodName : controllerKey + SLASH + methodName;
					}
					
					Action action = new Action(controllerKey, actionKey, controllerClass, method, methodName, actionInters, route.getFinalViewPath(routes.getBaseViewPath()));
					if (mapping.put(actionKey, action) != null) {
						throw new RuntimeException(buildMsg(actionKey, controllerClass, method));
					}
				}
			}
		}*/
		// 路由集合
		getRoutesList().stream().forEach(routes -> {
			
			// 每个路由对象里可能有多个子路由
			List<Route> routeItemList = routes.getRouteItemList();
			
			routeItemList.stream().forEach(route -> {
				
				// 获得路由对象关联的controller
				Class<? extends Controller> controllerClass = route.getControllerClass();
				
				// 此方法会根据controller上的before注解来获取相应的拦截类
				Interceptor[] controllerInters = interMan.createControllerInterceptor(controllerClass);
				
				// 判断controller是否是Contoller.class的子类
				boolean sonOfController = (controllerClass.getSuperclass() == Controller.class);
				
				// 如果是Contoller.class的子类：controllerClass.getDeclaredMethods()，类自身声明的所有方法
				// 如果不是Contoller.class的子类：controllerClass.getMethods()，类的所有共有方法
				List<Method> methods = Lists.newArrayList(sonOfController ? controllerClass.getDeclaredMethods() : controllerClass.getMethods());

				// 不建议将带有break、continue的循环放入stream.forEach处理，先用filter过滤掉
				methods.stream().filter(method -> {
					String methodName = method.getName();
					// 如果这是一个无参的方法
					boolean test1 = excludedMethodName.contains(methodName) || method.getParameterTypes().length != 0;
					// 是Contoller.class的子类，并且是一个public的方法
					boolean test2 = sonOfController && !Modifier.isPublic(method.getModifiers());
					// 两个都是false才可以继续执行
					return ( !test1 && !test2 );
				}).forEach(method -> {
					String methodName = method.getName();
					// 方法级的拦截器
					Interceptor[] actionInters = interMan.buildControllerActionInterceptor(routes.getInterceptors(), controllerInters, controllerClass, method);
					// contoller的键值？就是router中配置的某controller的根请求路径
					String controllerKey = route.getControllerKey();
					// 是否给该方法配置了独立路径
					ActionKey ak = method.getAnnotation(ActionKey.class);
					String actionKey;
					if (ak != null) {
						// 如果配置了
						actionKey = ak.value().trim();
						// 但没有配路径明细
						if ("".equals(actionKey))
							throw new IllegalArgumentException(controllerClass.getName() + "." + methodName + "(): The argument of ActionKey can not be blank.");
						// 如果忘记加"/"要补上
						if (!actionKey.startsWith(SLASH))
							actionKey = SLASH + actionKey;
					}
					// 如果没有配置，但是是index方法的话
					else if (methodName.equals("index")) {
						// 这个index方法就是根请求路径
						actionKey = controllerKey;
					}
					else {
						// 其余的方法若配置的根路径是"/"那么路径就是   /method   不然就是controllerKey/method
						actionKey = controllerKey.equals(SLASH) ? SLASH + methodName : controllerKey + SLASH + methodName;
					}
					// 一个基本的Action对象包括 
					// controllerKey根请求路径, actionKey子请求路径, 
					// controllerClass实际处理类, method实际处理方法, methodName方法名, 
					// actionInters方法级拦截器，模板路径
					Action action = new Action(controllerKey, actionKey, controllerClass, method, methodName, actionInters, route.getFinalViewPath(routes.getBaseViewPath()));
					// 放入映射集合中
					if (mapping.put(actionKey, action) != null) {
						throw new RuntimeException(buildMsg(actionKey, controllerClass, method));
					}
				});
			});
		});
		// 清空一下
		routes.clear();
		
		// support url = controllerKey + urlParas with "/" of controllerKey
		Action action = mapping.get("/");
		if (action != null) {
			mapping.put("", action);
		}
	}
	
	private static final String buildMsg(String actionKey, Class<? extends Controller> controllerClass, Method method) {
		StringBuilder sb = new StringBuilder("The action \"")
			.append(controllerClass.getName()).append(".")
			.append(method.getName()).append("()\" can not be mapped, ")
			.append("actionKey \"").append(actionKey).append("\" is already in use.");
		
		String msg = sb.toString();
		System.err.println("\nException: " + msg);
		return msg;
	}
	
	/**
	 * Support four types of url							  <br>
	 * 1: http://abc.com/controllerKey                 ---> 00<br>
	 * 2: http://abc.com/controllerKey/para            ---> 01<br>
	 * 3: http://abc.com/controllerKey/method          ---> 10<br>
	 * 4: http://abc.com/controllerKey/method/para     ---> 11<br>
	 * The controllerKey can also contains "/"				  <br>
	 * Example: http://abc.com/uvw/xyz/method/para
	 */
	Action getAction(String url, String[] urlPara) {
		// 根据url获取mapping中的action
		Action action = mapping.get(url);
		// 如果能获取到就直接返回去
		if (action != null) {
			return action;
		}
		// 没有获取到时
		// 最后一个斜杠出现的位置
		int i = url.lastIndexOf(SLASH);
		if (i != -1) {
			// 将最后一个斜杠之前的作为请求路径查找action
			action = mapping.get(url.substring(0, i));
			// 斜杠后的作为参数
			urlPara[0] = url.substring(i + 1);
		}
		
		return action;
	}
	
	List<String> getAllActionKeys() {
		List<String> allActionKeys = new ArrayList<String>(mapping.keySet());
		Collections.sort(allActionKeys);
		return allActionKeys;
	}
}